package com.pubinfo.passbook.engine.chain;

import com.pubinfo.passbook.common.exception.ServiceException;
import com.pubinfo.passbook.engine.Computor;
import com.pubinfo.passbook.engine.cache.ComputeCache;
import com.pubinfo.passbook.engine.cell.ComputeUnit;
import com.pubinfo.passbook.engine.utils.ValidateUtils;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * 计算链
 *
 * <p>
 * 计算的部分基本所有链中都是相同的，
 * 通常在最终实现的计算链类中只需要明确计算类型和部分赋值方法。
 * 如果有需要，则重写具体的计算方法。
 * </p>
 *
 * @author zhouch
 * @date 2021/12/13 10:28
 */
@Slf4j
public abstract class ComputeChain<T, R> implements Computor<T, R> {

    @Setter
    protected ComputeCache cache = new ComputeCache();

    protected Collection<ComputeUnit<T, R>> units = Arrays.asList();

    /**
     * 装载计算单元或者计算链
     */
    public void loadUnits(Collection<ComputeUnit<T, R>> units) {
        this.units = units;
    }


    /**
     * 原本想使用链表结构的计算链，但是调试十分不方便，还是使用了数组型。
     *
     * @param data
     * @param beforeVal
     * @return
     */
    @Override
    public R compute(final T data, R beforeVal) {
        checkUnitAvailable();

        AtomicReference<R> result = new AtomicReference<>(beforeVal);

        for (ComputeUnit<T, R> computeUnit : units) {
            // 重新设置缓存
            computeUnit.setCache(cache);
            log.info("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
            log.info("< 运行计算单元：{}, 输入值: {} ", computeUnit.getClass().getSimpleName(), result.get().toString());
            log.info("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");

            result.set(computeUnit.compute(data, result.get()));

            log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            log.info("> 运行计算单元：{}, 返回值: {} ", computeUnit.getClass().getSimpleName(), result.get().toString());
            log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        }

        return result.get();
    }


    /**
     * 检查计算单元可用性.
     * <p>
     * Note: 保存修改计算链以及执行计算链前请执行该函数。
     * <p>
     * 如果检查存在错误则直接抛出运行时异常
     *
     * @return
     */
    protected void checkUnitAvailable() {
        if (units.isEmpty()) {
            return;
        }

        if (false == ValidateUtils.checkChainType(units, getChainType())) {
            String allUnitChainType = units.stream().map(ComputeUnit::getChainType).collect(Collectors.joining(", "));
            throw new ServiceException("链内的计算单元类型不匹配: 所有计算单元目标计算链类型：" + allUnitChainType + "; 目标计算链类型：" + getChainType());
        }
    }

    /**
     * 释放计算缓存
     */
    public void down() {
    }


    @Override
    protected void finalize() {
        this.down();
    }
}
